package com.joizhang.seckill.limit;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.RateLimiter;
import com.joizhang.seckill.api.service.exception.ServiceException;
import com.joizhang.seckill.utils.IPUtils;
import com.joizhang.seckill.utils.WebCodeMsg;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
@Aspect
public class RateLimiterAspect {

    private static final LoadingCache<String, RateLimiter> CACHES = CacheBuilder.newBuilder()
            .maximumSize(1000)
            .expireAfterAccess(1, TimeUnit.DAYS)
            .build(new CacheLoader<String, RateLimiter>() {
                @Override
                public RateLimiter load(String key) throws Exception {
                    return RateLimiter.create(5);
                }
            });

    @Pointcut("@annotation(com.joizhang.seckill.limit.ApiRateLimiter)")
    public void rateLimit() {

    }

    @Around("rateLimit()")
    public Object pointcut(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解
        ApiRateLimiter apiRateLimiter = AnnotationUtils.findAnnotation(method, ApiRateLimiter.class);
        if (apiRateLimiter != null && apiRateLimiter.qps() > ApiRateLimiter.NOT_LIMITED) {
            LimitType limitType = apiRateLimiter.limitType();
            RateLimiter rateLimiter;
            switch (limitType) {
                case IP:
                    String key = IPUtils.getIpAddr();
                    rateLimiter = CACHES.getUnchecked(key);
                    break;
                case METHOD:
                    double qps = apiRateLimiter.qps();
                    rateLimiter = CACHES.getIfPresent(method.getName());
                    if (Objects.isNull(rateLimiter)) {
                        CACHES.put(method.getName(), RateLimiter.create(qps));
                        rateLimiter = CACHES.get(method.getName());
                    }
                    log.debug("{}的QPS设置为: {}", method.getName(), rateLimiter.getRate());
                    break;
                default:
                    throw new IllegalArgumentException();
            }
            if (!rateLimiter.tryAcquire(apiRateLimiter.timeout(), apiRateLimiter.timeUnit())) {
                throw new ServiceException(WebCodeMsg.ACCESS_LIMIT_REACHED);
            }
        }
        return point.proceed();
    }

}
